package com.springmvc.shangguigu.controller;

import com.springmvc.shangguigu.entities.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;

import java.util.Arrays;
import java.util.Date;
import java.util.Map;

/**
 * SpringMvcResponseController--SpringMVC-test
 *
 * @author: Jovi
 * @createTime: 2018-09-04 11:15
 **/
@Controller
@RequestMapping("/springmvcResponse")
@SessionAttributes(value = "user",types = String.class)
public class SpringMvcResponseController {

    private static final String SUCCESS = "shangguigu/success";

    /**
     * SpringMVC 会将ModelAndView的model中数据放入到request 域对象中
     * @return
     */
    @RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView() {
        String viewName = SUCCESS;
        ModelAndView modelAndView = new ModelAndView(viewName);

        modelAndView.addObject("time1", new Date());
        return modelAndView;
    }

    /**
     * 目标方法可以添加MAP 类型（实际上也可以是Model类型或ModelMap类型）的参数
     * @param map
     * @return
     */
    @RequestMapping("/testMap")
    public String testMap(Map<String,Object> map) {
        System.out.println(map.getClass().getName());
        map.put("names", Arrays.asList("Tom", "Jerry", "cat"));
        return SUCCESS;
    }

    /**
     * @SessionAttributes
     * 除了可以通过属性名指定需要放到会话中的属性外（实际上使用的是value属性值），
     * 还可以通过模型属性的对象类型指定哪些模型属性需要放到绘画中（实际上使用的是types属性值）
     *
     * 注意：该注解只能放在类的上面，而不能放在方法上面
     * @param map
     * @return
     */
    @RequestMapping("/testSessionAttributes")
    public String testSessionAttributes(Map<String,Object> map) {
        User user = User.builder().userName("Tom").age(15).password("123").email("123@123.com").build();
        map.put("user", user);
        map.put("school", "guigu");
        map.put("certNo", 123);
        return SUCCESS;
    }

    /**
     * 运行流程：
     * 1.执行@ModelAttribute注解修饰的方法，从数据库中取出对象，把对象放入到Map中，KEY为：user
     * 2.SpringMvc 从map中取出User对象，并把表单的请求参数赋给改User对象的对应属性
     * 3.SpringMvc 把上述对象传入目标方法的参数
     *
     * 注意：在@ModelAttribute修饰的方法中，放入到Map时的Key需要和目标
     * 方法入参类型的第一个字母小写的字符串一致
     *
     *
     * SpringMVC确定目标方法pojo类型入参的过程
     * 1.确定一个key:
     *      1).若目标方法的pojo类型的参数没有使用@ModelAttribute修饰，则key为pojo类名第一个字母的小写
     *      2).若使用了@ModelAttribute来修饰，则key为@ModelAttribute注解的value属性值
     * 2.在implicitModel中查找对应的对象，若存在，则作为入参传入
     * 3.若implicitModel中不存在对应的对象，则检查当前的Handler是否使用@SessionAttributes修饰，
     * 若使用了该注解，且@SessionAttributes注解的value属性值中包含了key，则会从HttpSession中
     * 来获取key所对应的value值，若存在则直接传入到目标方法的入参中，若不存在则抛出异常
     * 4.若Handler没有标识@SessionAttributes注解或@SessionAttributes注解的value值中不包含key，
     * 则会通过凡是来创建pojo类型的参数，传入为目标方法的参数
     * 5.SpringMVC会把key和value保存到implicitModel中，进而会保存到request中
     *
     *
     *
     * 源代码分析流程：
     * 1.首先会去调用@ModelAttribute注解修饰的方法。实际上把@ModelAttribute方法中Map中的数据放在了
     * implicitModel中。
     * 2.解析请求处理器的目标参数，实际上该目标参数来自于WebDataBinder对象的target属性
     *      1).创建webDataBinder对象：
     *          ①确定objectName属性：若传入的attrName属性值为空，则objectName为类名第一个字母小写
     *          注意：attrName 若目标方法pojo属性使用了@ModelAttribute来修饰，则attrName值即为@ModelAttribute
     *          的value属性值
     *          ②确定target属性
     *          在implicitModel中查找attrName属性值。若存在OK
     *          若不存在：则验证当前Handler是否使用了@SessionAttributes进行修饰，若使用了，则尝试从Session中
     *          获取attrName所对应的属性值。若session中没有对应的属性值，则抛出异常
     *          若Handler 没有使用@SessionAttributes进行修饰，或@SessionAttributes中没有使用value值
     *          指定的key和attrName相匹配，则通过反射创建了pojo对象
     *      2).SpringMvc 把请求表单的请求参数赋给了WebDataBinder的target对应的属性。
     *      3).SpringMvc 把WebDataBinder的attrName 和 target 给到implicitModel
     *      4).把webDataBinder的target作为参数传递给目标方法
     * @param user
     * @return
     */
    @RequestMapping("/testModelAttribute")
    public String testModelAttribute(User user) {
        System.out.println("修改："+user);
        return SUCCESS;
    }

    /**
     * 1.有@ModelAttribute标记的方法，会在每个目标方法执行之前，被SpringMvc调用
     * 2.@ModelAttribute注解也可以修饰目标方法pojo的入参，其value属性值有如下作用：
     *      1).springMvc会使用value属性值在implicitModel中查找对应的对象，若存在则会直接传入到目标方法的入参中
     *      2).springMvc会value为key，pojo类型的对象为value，存入到request
     * @param id
     * @param map
     */
    @ModelAttribute
    public void getUser(@RequestParam(value = "id",required = false)Integer id, Map<String,Object> map) {
        if (id != null) {
            //模拟从数据库中获取对象
            User tom = User.builder().id(1).userName("Tom").email("123@123").password("123456").age(12).build();
            System.out.println("从数据库获取一个对象："+tom);
            map.put("user", tom);
        }
    }
}
